<pre class=metadata>
Title: Event Timing API
Status: CG-DRAFT
Shortname: event-timing
Group: WICG
Level: 1
Editor: Nicolás Peña Moreno, Google https://google.com, npm@chromium.org
        Tim Dresser, Google https://google.com, tdresser@chromium.org
URL: https://wicg.github.io/event-timing
Repository: https://github.com/WICG/event-timing
Test Suite: https://github.com/web-platform-tests/wpt/tree/master/event-timing
Abstract: This document defines an API that provides web page authors with insights into the latency of certain events triggered by user interactions.
Default Highlight: js
Complain About: accidental-2119 yes
</pre>

<pre class=anchors>
urlPrefix: https://w3c.github.io/performance-timeline/; spec: PERFORMANCE-TIMELINE-2;
    type: interface; url: #the-performanceentry-interface; text: PerformanceEntry;
    type: attribute; for: PerformanceEntry;
        text: name; url: #dom-performanceentry-name;
        text: entryType; url: #dom-performanceentry-entrytype;
        text: startTime; url: #dom-performanceentry-starttime;
        text: duration; url: #dom-performanceentry-duration;
    type: dfn; url: #dfn-register-a-performance-entry-type; text: register a performance entry type;
    type: dfn; url: #dfn-queue-a-performanceentry; text: queue the entry;
    type: attribute; for: PerformanceObserver;
        text: supportedEntryTypes; url: #supportedentrytypes-attribute;
urlPrefix: https://w3c.github.io/hr-time/; spec: HR-TIME-2;
    type: typedef; url: #idl-def-domhighrestimestamp; text: DOMHighResTimeStamp;
    type: interface; url: #dfn-performance; text: Performance;
    type: method; for:Performance;
        text: now(); url: #dom-performance-now;
    type: dfn; text: current high resolution time; url: #dfn-current-high-resolution-time;
    type: attribute; for: WindowOrWorkerGlobalScope;
        text: performance; url: #dom-windoworworkerglobalscope-performance;
urlPrefix: https://tc39.github.io/ecma262/; spec: ECMASCRIPT;
    type: dfn; url: #sec-math.round; text: Math.round;
urlPrefix: https://dom.spec.whatwg.org/; spec: DOM;
    type: attribute; for: Event;
        text: type; url: #dom-event-type;
        text: timeStamp; url: #dom-event-timestamp;
        text: cancelable; url: #dom-event-cancelable;
        text: isTrusted; url: #dom-event-istrusted;
    type: dfn; url: #concept-event-dispatch; text: event dispatch algorithm
urlPrefix: https://w3c.github.io/pointerevents/; spec: POINTEREVENTS;
    type: event; url: #the-pointerover-event; text: pointerover;
    type: event; url: #the-pointerenter-event; text: pointerenter;
    type: event; url: #the-pointerdown-event; text: pointerdown;
    type: event; url: #the-pointermove-event; text: pointermove;
    type: event; url: #the-pointerup-event; text: pointerup;
    type: event; url: #the-pointercancel-event; text: pointercancel;
    type: event; url: #the-pointerout-event; text: pointerout;
    type: event; url: #the-pointerleave-event; text: pointerleave;
    type: event; url: #the-gotpointercapture-event; text: gotpointercapture;
    type: event; url: #the-lostpointercapture-event; text: lostpointercapture;
urlPrefix: https://w3c.github.io/touch-events/; spec: TOUCH-EVENTS;
    type: interface; url: #touchevent-interface; text: TouchEvent;
    type: event; url: #the-touchstart-event; text: touchstart;
    type: event; url: #the-touchend-event; text: touchend;
    type: event; url: #the-touchmove-event; text: touchmove;
    type: event; url: #the-touchcancel-event; text: touchcancel;
urlPrefix: https://w3c.github.io/paint-timing/; spec: PAINT-TIMING;
    type: dfn; url: #mark-paint-timing; text: mark paint timing;
urlPrefix: https://w3c.github.io/uievents/; spec: UIEVENTS;
    type: event; url: #event-type-auxclick; text: auxclick;
    type: event; url: #event-type-click; text: click;
    type: event; url: #event-type-dblclick; text: dblclick;
    type: event; url: #event-type-mousedown; text: mousedown;
    type: event; url: #event-type-mouseenter; text: mouseenter;
    type: event; url: #event-type-mouseleave; text: mouseleave;
    type: event; url: #event-type-mousemove; text: mousemove;
    type: event; url: #event-type-mouseout; text: mouseout;
    type: event; url: #event-type-mouseover; text: mouseover;
    type: event; url: #event-type-mouseup; text: mouseup;
    type: event; url: #event-type-keydown; text: keydown;
    type: event; url: #event-type-keyup; text: keyup;
    type: event; url: #event-type-wheel; text: wheel;
    type: event; url: #event-type-beforeinput; text: beforeinput;
    type: event; url: #event-type-input; text: input;
    type: event; url: #event-type-compositionstart; text: compositionstart;
    type: event; url: #event-type-compositionupdate; text: compositionupdate;
    type: event; url: #event-type-compositionend; text: compositionend;
urlPrefix: https://html.spec.whatwg.org/multipage/; spec: HTML;
    type: event; url: #event-dnd-drag; text: drag;
    type: event; url: #event-dnd-dragstart; text: dragstart;
    type: event; url: #event-dnd-dragenter; text: dragenter;
    type: event; url: #event-dnd-dragexit; text: dragexit;
    type: event; url: #event-dnd-dragleave; text: dragleave;
    type: event; url: #event-dnd-dragover; text: dragover;
    type: event; url: #event-dnd-drop; text: drop;
    type: event; url: #event-dnd-dragend; text: dragend;
</pre>

Introduction {#sec-intro}
=====================

<div class="non-normative">

<em>This section is non-normative.</em>

When a user engages with a website, they expect their actions to cause changes to the website quickly.
In fact, <a href=https://www.nngroup.com/articles/response-times-3-important-limits/>research</a> suggests that any user input that is not handled within 100ms is considered slow.
Therefore, it is important to surface input events that could not achieve those guidelines.

A common way to monitor event latency consists of registering an event listener.
The timestamp at which the event was created can be obtained via the event's {{Event/timeStamp}}.
In addition, {{Performance/now()|performance.now()}} could be called both at the beginning and at the end of the event handler logic.
By subtracting the hardware timestamp from the timestamp obtained at the beginning of the event handler,
the developer can compute the <b>input delay</b>: the time it takes for an input to start being processed.
By subtracting the timestamp obtained at the beginning of the event handler from the timestamp obtained at the end of the event handler,
the developer can compute the amount of synchronous work performed in the event handler.
Finally, when inputs are handled synchronously, the duration from event hardware timestamp to the next paint after the event is handled is a useful user experience metric.

This approach has several fundamental flaws.
First, requiring event listeners precludes measuring event latency very early in the page load because
listeners will likely not be registered yet at that point in time.
Second, developers that are only interested in the input delay might be forced to add new listeners to events that originally did not have one.
This adds unnecessary performance overhead to the event latency calculation.
And lastly, it would be very hard to measure asynchronous work caused by the event via this approach.

This specification provides an alternative to event latency monitoring that solves some of these problems.
Since the user agent computes the timestamps, there is no need for event listeners in order to measure performance.
This means that even events that occur very early in the page load can be captured.
This also enables visibility into slow events without requiring analytics providers to attempt to patch and subscribe to every conceivable event.
In addition to this, the website's performance will not suffer from the overhead of unneeded event listeners.
Finally, this specification allows developers to obtain detailed information about the timing of
the rendering that occurs right after the event has been processed.
This can be useful to measure the overhead of website modifications that are triggered by events.

The very first user interaction has a disproportionate impact on user experience, and is often disproportionately slow.
It's slow because it's often blocked on JavaScript execution that is not properly split into chunks during page load.
The latency of the website's response to the first user interaction can be considered a key responsiveness and loading metric.
To that effect, this API surfaces all the timing information about this interaction, even when this interaction is not handled slowly.
This allows developers to measure percentiles and improvements without having to register event handlers.
In particular, this API enables measuring the <b>first input delay (FID)</b>, the delay in processing for the first "discrete" input event.

</div>

Events exposed {#sec-events-exposed}
------------------------

The Event Timing API exposes timing information for certain events.
Certain types of events are considered, and timing information is exposed when the time difference between user input and paint operations that follow input processing exceeds a certain threshold.

<div algorithm="considered for Event Timing">
    Given an <var>event</var>, to determine if it should be <dfn>considered for Event Timing</dfn>, run the following steps:
    1. If <var>event</var>'s {{Event/isTrusted}} attribute value is set to false, return false.
    1. If <var>event</var>'s {{Event/type}} is one of the following:
    <!-- MouseEvents -->
        {{auxclick}}, {{click}}, {{dblclick}}, {{mousedown}}, {{mouseenter}}, {{mouseleave}}, {{mouseout}}, {{mouseover}}, {{mouseup}},
    <!-- PointerEvents -->
        {{pointerover}}, {{pointerenter}}, {{pointerdown}}, {{pointerup}}, {{pointercancel}}, {{pointerout}}, {{pointerleave}}, {{gotpointercapture}}, {{lostpointercapture}}
    <!-- TouchEvents -->
        {{touchstart}}, {{touchend}}, {{touchcancel}},
    <!-- KeyboardEvents -->
        {{keydown}}, {{keyup}},
    <!-- WheelEvents excluded for now since they can be continuous. -->
    <!-- InputEvents -->
        {{beforeinput}}, {{input}},
    <!-- CompositionEvents -->
        {{compositionstart}}, {{compositionupdate}}, {{compositionend}},
    <!-- Drag and Drop -->
         {{dragstart}}, {{dragend}}, {{dragenter}}, {{dragexit}}, {{dragleave}}, {{dragover}}, {{drop}},
        return true.
    1. Return false.
</div>

Note: {{mousemove}}, {{pointermove}}, {{touchmove}}, {{wheel}}, and {{drag}} are excluded because these are "continuous" events.
The current API does not have enough guidance on how to count and aggregate these events to obtain meaningful performance metrics based on entries.
Therefore, these event types are not exposed.

The Event Timing API also exposes timing information about the first user interaction among the following:
* {{keydown}}
* {{mousedown}}
* {{pointerdown}} which is followed by {{pointerup}}
* {{click}}

Usage example {#sec-example}
------------------------

<pre class="example highlight">
    const observer = new PerformanceObserver(function(list) {
        const perfEntries = list.getEntries().forEach(entry => {
            const inputDelay = entry.processingStart - entry.startTime;
            // Report the input delay when the processing start was provided.
            // Also report the full input duration via entry.duration.
        });
    });
    // Register observer for event.
    observer.observe({entryTypes: ["event"]});
    ...
    // We can also directly query the first input information.
    new PerformanceObserver(function(list, obs) {
        const firstInput = list.getEntries()[0];

        // Measure the delay to begin processing the first input event.
        const firstInputDelay = firstInput.processingStart - firstInput.startTime;
        // Measure the duration of processing the first input event.
        // Only use when the important event handling work is done synchronously in the handlers.
        const firstInputDuration = firstInput.duration;
        // Process the first input delay and perhaps its duration...

        // Disconnect this observer since callback is only triggered once.
        obs.disconnect();
    }).observe({type: 'first-input', buffered: true});

}
</pre>

The following are sample use cases that could be achieved by using this API:
* Gather FID data (see <code>firstInputDelay</code> on the example above) on a website and track its performance over time.
* Clicking a button changes the sorting order on a table. Measure how long it takes from the click until we display reordered content.
* A user drags a slider to control volume. Measure the latency to drag the slider.
* Hovering a menu item triggers a flyout menu. Measure the latency for the flyout to appear.
* Measure the 75'th percentile of the latency of the first user click (whenever click happens to be the first user interaction).

Event Timing {#sec-event-timing}
=======================================

Event Timing adds the following interfaces:

{{PerformanceEventTiming}} interface {#sec-performance-event-timing}
------------------------------------------------------------------------

<pre class="idl">
[Exposed=Window]
interface PerformanceEventTiming : PerformanceEntry {
    readonly attribute DOMHighResTimeStamp processingStart;
    readonly attribute DOMHighResTimeStamp processingEnd;
    readonly attribute boolean cancelable;
    [Default] object toJSON();
};
</pre>

Note: A user agent implementing the Event Timing API would need to include "<code>first-input</code>" and "<code>event</code>" in {{PerformanceObserver/supportedEntryTypes}} for {{Window}} contexts.
This allows developers to detect support for event timing.

<div class="non-normative">

<em>
    This remainder of this section is non-normative.
    The values of the attributes of {{PerformanceEventTiming}} are set in the processing model in [[#sec-processing-model]].
    This section provides an informative summary of how they will be set.
</em>

Each {{PerformanceEventTiming}} object reports timing information about an <dfn for=PerformanceEventTiming>associated {{Event}}</dfn>.

{{PerformanceEventTiming}} extends the following attributes of the {{PerformanceEntry}} interface:

<dl>
    <dt>{{PerformanceEntry/name}}</dt>
    <dd>The {{PerformanceEntry/name}} attribute's getter provides the <a>associated event</a>'s {{Event/type}}.</dd>
    <dt>{{PerformanceEntry/entryType}}</dt>
    <dd>The {{PerformanceEntry/entryType}} attribute's getter returns "<code>event</code>" (for long events) or "<code>first-input</code>" (for the first user interaction).</dd>
    <dt>{{PerformanceEntry/startTime}}</dt>
    <dd>The {{PerformanceEntry/startTime}} attribute's getter returns the <a>associated event</a>'s {{Event/timeStamp}}.</dd>
    <dt>{{PerformanceEntry/duration}}</dt>
    <dd>The {{PerformanceEntry/duration}} attribute's getter returns the difference between
    the time of the first <a>update the rendering</a> step occurring after <a>associated event</a> has been dispatched
    and the {{PerformanceEntry/startTime}}, rounded to the nearest 8ms.</dd>
</dl>

{{PerformanceEventTiming}} has the following additional attributes:

<dl dfn-type=attribute dfn-for=PerformanceEventTiming link-for=PerformanceEventTiming>
    <dt>{{processingStart}}</dt>
    <dd>
        The <dfn export>processingStart</dfn> attribute's getter returns a timestamp captured at the beginning of the <a link-for=''>event dispatch algorithm</a>. This is when event handlers are about to be executed.
    </dd>
    <dt>{{processingEnd}}</dt>
    <dd>
        The <dfn export>processingEnd</dfn> attribute's getter returns a timestamp captured at the end of the <a link-for=''>event dispatch algorithm</a>. This is when event handlers have finished executing. It's equal to {{processingStart}} when there are no such event handlers.
    </dd>
    <dt>{{cancelable}}</dt>
    <dd>
        The <dfn export>cancelable</dfn> attribute's getter returns the <a>associated event</a>'s {{Event/cancelable}} attribute value.
    </dd>
</dl>

</div>

{{EventCounts}} interface {#sec-event-counts}
------------------------

<pre class="idl">
[Exposed=Window]
interface EventCounts {
    readonly maplike&lt;DOMString, unsigned long long&gt;;
};
</pre>

The {{EventCounts}} object is a map where the keys are event <a href=Event/type>types</a> and the values are the number of events that have been dispatched that are of that {{Event/type}}.
Only events whose {{Event/type}} is supported by {{PerformanceEventTiming}} entries (see section [[#sec-events-exposed]]) are counted via this map.

Extensions to the {{Performance}} interface {#sec-extensions}
------------------------

<pre class="idl">
[Exposed=Window]
partial interface Performance {
    [SameObject] readonly attribute EventCounts eventCounts;
};
</pre>

The {{Performance/eventCounts}} attribute's getter returns a map with entries of the form <var>type</var> → <var>numEvents</var>.
This means that there have been <var>numEvents</var> dispatched such that their {{Event/type}} attribute value is equal to <var>type</var>.

Processing model {#sec-processing-model}
========================================

Modifications to the DOM specification {#sec-modifications-DOM}
--------------------------------------------------------

<em>This section will be removed once the <a href=https://dom.spec.whatwg.org>DOM specification</a> has been modified.</em>

<div algorithm="additions to event dispatch">
    We modify the <a>event dispatch algorithm</a> as follows.

    Right after step 1, we add the following step:

    * Let <var>timingEntry</var> be the result of <a lt='initialize event timing'>initializing event timing</a> given <em>event</em> and the <a>current high resolution time</a>.

    Right before the returning step of that algorithm, add the following step:

    * <a>Finalize event timing</a> passing <var>timingEntry</var>, <em>target</em>, and the <a>current high resolution time</a> as inputs.
</div>

Note: If a user agent skips the <a>event dispatch algorithm</a>, it can still choose to include an entry for that {{Event}}.
In this case, it will estimate the value of {{PerformanceEventTiming/processingStart}} and set the {{PerformanceEventTiming/processingEnd}} to the same value.

Modifications to the HTML specification {#sec-modifications-HTML}
--------------------------------------------------------

<em>This section will be removed once the <a href=https://html.spec.whatwg.org/multipage>HTML specification</a> has been modified.</em>

Each {{Window}} has <dfn>pending event entries</dfn>, a list that stores {{PerformanceEventTiming}} objects, which will initially be empty.
Each {{Window}} also has <dfn>pending pointer down</dfn>, a pointer to a {{PerformanceEventTiming}} entry which is initially null.
Finally, each {{Window}} has <dfn>has dispatched input event</dfn>, a boolean which is initially set to false.

<div algorithm="additions to update rendering">
    In the <a>update the rendering</a> step of the <a>event loop processing model</a>, add a step right after the step that calls <a>mark paint timing</a>:

    1. For each <a>fully active</a> {{Document}} in <em>docs</em>, invoke the algorithm to <a>dispatch pending Event Timing entries</a> for that {{Document}}.
</div>

Initialize event timing {#sec-init-event-timing}
--------------------------------------------------------

<div algorithm="initialize event timing">
    When asked to <dfn export>initialize event timing</dfn>, with <var>event</var> and <var>processingStart</var> as inputs, run the following steps:

    1. If the algorithm to determine if <var>event</var> should be <a>considered for Event Timing</a> returns false, then return null.
    1. Let <var>timingEntry</var> be a new {{PerformanceEventTiming}} object.
    1. Set <var>timingEntry</var>'s {{PerformanceEntry/name}} to <var>event</var>'s {{Event/type}} attribute value.
    1. Set <var>timingEntry</var>'s {{PerformanceEntry/entryType}} to "<code>event</code>".
    1. Set <var>timingEntry</var>'s {{PerformanceEntry/startTime}} to <var>event</var>'s {{Event/timeStamp}} attribute value.
    1. Set <var>timingEntry</var>'s {{processingStart}} to <var>processingStart</var>.
    1. Set <var>timingEntry</var>'s {{cancelable}} to <var>event</var>'s {{Event/cancelable}} attribute value.
    1. Return <var>timingEntry</var>.
</div>

Finalize event timing {#sec-fin-event-timing}
--------------------------------------------------------

<div algorithm="finalize event timing">
    When asked to to <dfn export>finalize event timing</dfn>, with <var>timingEntry</var>, <var>target</var>, and <var>processingEnd</var> as inputs, run the following steps:

    1. If <var>timingEntry</var> is null, return.
    1. Let <var>relevantGlobal</var> be <var>target</var>'s <a>relevant global object</a>.
    1. If <var>relevantGlobal</var> does not <a>implement</a> {{Window}}, return.
    1. Set <var>timingEntry</var>'s {{processingEnd}} to <var>processingEnd</var>.
    1. Append <var>timingEntry</var> to <var>relevantGlobal</var>’s <a>pending event entries</a>.
</div>

Dispatch pending Event Timing entries {#sec-dispatch-pending}
--------------------------------------------------------

<div algorithm="dispatch pending Event Timing entries">
    When asked to <dfn export>dispatch pending Event Timing entries</dfn> for a {{Document}} <var>doc</var>, run the following steps:

    1. Let <var>window</var> be <var>doc</var>'s <a>relevant global object</a>.
    1. Let <var>renderingTimestamp</var> be the <a>current high resolution time</a>.
    1. For each <var>timingEntry</var> in <var>window</var>'s <a>pending event entries</a>:
        1. Let <var>start</var> be <var>timingEntry</var>'s {{PerformanceEntry/startTime}} attribute value.
        1. Set <var>timingEntry</var>'s {{PerformanceEntry/duration}} by running the following steps:
            1. Let <var>difference</var> be <code><var>renderingTimestamp</var> - <var>start</var></code>.
            1. Set <var>timingEntry</var>'s {{PerformanceEntry/duration}} to the result of rounding <var>difference</var> to the nearest multiple of 8.
        1. Let <var>name</var> be <var>timingEntry</var>'s {{PerformanceEntry/name}} attribute value.
        1. Perform the following steps to update the event counts:
            1. Let <var>performance</var> be <var>window</var>'s {{WindowOrWorkerGlobalScope/performance}} attribute value.
            1. If <var>performance</var>'s {{Performance/eventCounts}} attribute value does not contain a <a>map entry</a> whose key is <var>name</var>, then:
                1. Let <var>mapEntry</var> be a new <a>map entry</a> with key equal to <var>name</var> and value equal to 1.
                1. Add <var>mapEntry</var> to <var>performance</var>'s {{Performance/eventCounts}} attribute value.
            1. Otherwise, increase the <a>map entry</a>'s value by 1.
        1. If <var>timingEntry</var>'s {{PerformanceEntry/duration}} attribute value is greater than or equal to 104, then <a lt='queue the entry'>queue</a> <var>timingEntry</var>.
        1. If <var>window</var>'s <a>has dispatched input event</a> is false, run the following steps:
            1. If <var>name</var> is "<code>pointerdown</code>", run the following steps:
                1. Set <var>window</var>'s <a>pending pointer down</a> to a copy of <var>timingEntry</var>.
                1. Set the {{PerformanceEntry/entryType}} of <var>window</var>'s <a>pending pointer down</a> to "<code>first-input</code>".
            1. Otherwise, run the following steps:
                1. If <var>name</var> is "<code>pointerup</code>" AND if <var>window</var>'s <a>pending pointer down</a> is not null, then:
                    1. Set <var>window</var>'s <a>has dispatched input event</a> to true.
                    1. <a lt='queue the entry'>Queue</a> <var>window</var>'s <a>pending pointer down</a>.
                1. Otherwise, if <var>name</var> is one of "<code>click</code>", "<code>keydown</code>" or "<code>mousedown</code>", then:
                    1. Set <var>window</var>'s <a>has dispatched input event</a> to true.
                    1. Let <var>newFirstInputDelayEntry</var> be a copy of <var>timingEntry</var>.
                    1. Set <var>newFirstInputDelayEntry</var>'s {{PerformanceEntry/entryType}} to "<code>first-input</code>".
                    1. <a>Queue the entry</a> <var>newFirstInputDelayEntry</var>.
</div>

Security & privacy considerations {#priv-sec}
===============================================

We would not like to introduce more high resolution timers to the web platform due to the security concerns entailed by such timers.
Event handler timestamps have the same accuracy as {{Performance/now()|performance.now()}}.
Since {{processingStart}} and {{processingEnd}} could be computed without using this API,
exposing these attributes does not produce new attack surfaces.
Thus, {{PerformanceEntry/duration}} is the only one which requires further consideration.

The {{PerformanceEntry/duration}} has an 8 millisecond granularity (it is computed as such by performing rounding).
Thus, a high resolution timer cannot be produced from this timestamps.
However, it does introduce new information that is not readily available to web developers: the time pixels draw after an event has been processed.
We do not find security or privacy concerns on exposing the timestamp, especially given its granularity.
In an effort to expose the minimal amount of new information that is useful, we decided to pick 8 milliseconds as the granularity.
This allows relatively precise timing even for 120Hz displays.

The choice of 104ms as the cutoff value for the {{PerformanceEntry/duration}} is just the first multiple of 8 greater than 100ms.
An event whose rounded duration is greater than or equal to 104ms will have its pre-rounded duration greater than or equal to 100ms.
Such events are not handled in accordance with the RAIL performance model, which suggests applications respond within 100ms to user input.
